//  TwoPointsCrossover.java
//
//  Author:
//       Antonio J. Nebro <antonio@lcc.uma.es>
//       Juan J. Durillo <durillo@lcc.uma.es>
//
//  Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
package jmetal.operators.crossover;

import jmetal.core.Solution;
import jmetal.encodings.solutionType.PermutationSolutionType;
import jmetal.encodings.variable.Permutation;
import jmetal.util.Configuration;
import jmetal.util.JMException;
import jmetal.util.PseudoRandom;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * This class allows to apply a two points crossover operator using two parent
 * solutions. NOTE: the type of the solutions must be Permutation..
 */
public class TwoPointsCrossover extends Crossover {

    /**
     * Valid solution types to apply this operator
     */
    private static final List VALID_TYPES = Arrays.asList(PermutationSolutionType.class);

    private Double crossoverProbability_ = null;

    /**
     * Constructor Creates a new intance of the two point crossover operator
     */
    public TwoPointsCrossover(HashMap<String, Object> parameters) {
        super(parameters);

        if (parameters.get("probability") != null) {
            crossoverProbability_ = (Double) parameters.get("probability");
        }
    } // TwoPointsCrossover

    /**
     * Constructor
     *
     * @param A properties containing the Operator parameters Creates a new
     * intance of the two point crossover operator
     */
	//public TwoPointsCrossover(Properties properties) {
    //	this();
    //}
    /**
     * Perform the crossover operation
     *
     * @param probability Crossover probability
     * @param parent1 The first parent
     * @param parent2 The second parent
     * @return Two offspring solutions
     * @throws JMException
     */
    public Solution[] doCrossover(double probability,
            Solution parent1,
            Solution parent2) throws JMException {

        Solution[] offspring = new Solution[2];

        offspring[0] = new Solution(parent1);
        offspring[1] = new Solution(parent2);

        if (parent1.getType().getClass() == PermutationSolutionType.class) {
            if (PseudoRandom.randDouble() < probability) {
                int crosspoint1;
                int crosspoint2;
                int permutationLength;
                int parent1Vector[];
                int parent2Vector[];
                int offspring1Vector[];
                int offspring2Vector[];

                permutationLength = ((Permutation) parent1.getDecisionVariables()[0]).getLength();
                parent1Vector = ((Permutation) parent1.getDecisionVariables()[0]).vector_;
                parent2Vector = ((Permutation) parent2.getDecisionVariables()[0]).vector_;
                offspring1Vector = ((Permutation) offspring[0].getDecisionVariables()[0]).vector_;
                offspring2Vector = ((Permutation) offspring[1].getDecisionVariables()[0]).vector_;

                // STEP 1: Get two cutting points
                crosspoint1 = PseudoRandom.randInt(0, permutationLength - 1);
                crosspoint2 = PseudoRandom.randInt(0, permutationLength - 1);

                while (crosspoint2 == crosspoint1) {
                    crosspoint2 = PseudoRandom.randInt(0, permutationLength - 1);
                }

                if (crosspoint1 > crosspoint2) {
                    int swap;
                    swap = crosspoint1;
                    crosspoint1 = crosspoint2;
                    crosspoint2 = swap;
                } // if

                // STEP 2: Obtain the first child
                int m = 0;
                for (int j = 0; j < permutationLength; j++) {
                    boolean exist = false;
                    int temp = parent2Vector[j];
                    for (int k = crosspoint1; k <= crosspoint2; k++) {
                        if (temp == offspring1Vector[k]) {
                            exist = true;
                            break;
                        } // if
                    } // for
                    if (!exist) {
                        if (m == crosspoint1) {
                            m = crosspoint2 + 1;
                        }
                        offspring1Vector[m++] = temp;
                    } // if
                } // for

                // STEP 3: Obtain the second child
                m = 0;
                for (int j = 0; j < permutationLength; j++) {
                    boolean exist = false;
                    int temp = parent1Vector[j];
                    for (int k = crosspoint1; k <= crosspoint2; k++) {
                        if (temp == offspring2Vector[k]) {
                            exist = true;
                            break;
                        } // if
                    } // for
                    if (!exist) {
                        if (m == crosspoint1) {
                            m = crosspoint2 + 1;
                        }
                        offspring2Vector[m++] = temp;
                    } // if
                } // for
            } // if 
        } // if
        else {
            Configuration.logger_.severe("TwoPointsCrossover.doCrossover: invalid "
                    + "type"
                    + parent1.getDecisionVariables()[0].getVariableType());
            Class cls = java.lang.String.class;
            String name = cls.getName();
            throw new JMException("Exception in " + name + ".doCrossover()");
        }

        return offspring;
    } // makeCrossover

    /**
     * Executes the operation
     *
     * @param object An object containing an array of two solutions
     * @return An object containing an array with the offSprings
     * @throws JMException
     */
    public Object execute(Object object) throws JMException {
        Solution[] parents = (Solution[]) object;
        Double crossoverProbability;

        if (!(VALID_TYPES.contains(parents[0].getType().getClass())
                && VALID_TYPES.contains(parents[1].getType().getClass()))) {

            Configuration.logger_.severe("TwoPointsCrossover.execute: the solutions "
                    + "are not of the right type. The type should be 'Permutation', but "
                    + parents[0].getType() + " and "
                    + parents[1].getType() + " are obtained");
        } // if 

        crossoverProbability = (Double) getParameter("probability");

        if (parents.length < 2) {
            Configuration.logger_.severe("TwoPointsCrossover.execute: operator needs two "
                    + "parents");
            Class cls = java.lang.String.class;
            String name = cls.getName();
            throw new JMException("Exception in " + name + ".execute()");
        }

        Solution[] offspring = doCrossover(crossoverProbability_,
                parents[0],
                parents[1]);

        return offspring;
    } // execute

} // TwoPointsCrossover
